﻿
package extremefx.ui {
	import extremefx.IEnumerator;
	import extremefx.data.Comprehension;
	import extremefx.data.DSItem;
	import extremefx.data.DataSource;
	import extremefx.events.EventArgs;
	import extremefx.events.EventHandler;
	import extremefx.events.KeyEventArgs;
	import extremefx.geom.Edge;
	import extremefx.geom.Size;
	import extremefx.ui.UIComponent;
	import extremefx.ui.styles.UIStates;
	
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.ui.Keyboard;
	import flash.utils.getTimer;
	import flash.utils.setTimeout;	

	/**
	 * @author Marcelo Volmaro
	 */
	public class ItemList extends UIComponent implements IListItemFactory {
		private var _vScrollBar:VScrollBar;
		private var _dataSource:DataSource;
		private var _items:Vector.<AbstractListItem>;
		private var _itemClass:IListItemFactory;
		private var _totalHeight:uint;
		private var _params:UIParameters;
		private var _visibleItems:uint;
		private var _userVisibleItems:uint;
		private var _mask:Rectangle;
		private var _contents:Sprite;
		private var _itemSize:Size;
		private var _oldStart:int;
		private var _listItemHeight:uint;
		
	
		private var _selectedDS:DSItem;
		private var _selectedUI:AbstractListItem;
		private var _tempSelIdx:int;
		private var _selIdx:int;
		private var _mouseWheel:Boolean;
		private var _ktimer:int;
		private var _word:String;
		private var _itemCount:uint;
		private var _visHeight:uint;
		private var _isFixedHeight:Boolean;
		private var _needsRebind:Boolean;

		public function ItemList(pParameters:UIParameters = null, pRenderer:IListItemFactory = null) {
			super();
			
			pParameters = initStyle("ItemList", pParameters);
			pParameters._parent = this;
			_params = pParameters; 
			_userVisibleItems = 10;
			_itemClass = pRenderer ? pRenderer : this;
			
			var ali:AbstractListItem = _itemClass.create(pParameters);
			_listItemHeight = ali.minimumSize.height;
			_isFixedHeight = ali.isFixedHeight;
			ali.dispose();
			
			_tempSelIdx = _selIdx = -1;
			
			_contents = new Sprite();
			_contents.x = _style.padding.left;
			_contents.y = _style.padding.top;
			
			_mSprite.addChild(_contents);
			
			var vsb:VScrollBar = _vScrollBar = new VScrollBar(pParameters);
			vsb.focusable(false);
			vsb.setKeyEnabled(false);
			vsb.useMouseWheel = false;
			vsb._componentSelect = focus;
			
			vsb.onScroll.add(_onScroll);
			
			_mSprite.addChild(vsb._visualUI);
		}
		
	
		public function setVisibleItemCount(pCount:uint):void {
			_userVisibleItems = pCount;
			invalidate();
		}
		
		public function setDataSource(pDataSource:DataSource):void{
			_dataSource = pDataSource;
			_itemCount = _dataSource.count - 1;
			_recalc();
		}
		
		public function create(pParams:UIParameters = null):AbstractListItem {
			return new ListItem(_params);
		}
		
		override public function set size(pSize:Size):void {
			_userVisibleItems = 0;
			super.size = pSize;
			_recalc();
		}
		
		private function _recalc():void {
			var vsbw:uint = _vScrollBar.width,
			vsbp:Edge = _vScrollBar.computedStyle.position,
			hs:uint = _width - vsbw - vsbp.right,
			vs:uint = _height,
			lih:uint = _listItemHeight,
			itemW:uint = _style.padding.getWidth(hs);
			
			_itemSize = new Size(itemW, lih);
			
			if (_userVisibleItems == 0){
				_visibleItems = Math.ceil(vs / lih) + 1;

			} else {
				_visibleItems = _userVisibleItems + 1;
				_height = vs = (_userVisibleItems * lih) + _style.padding.top + _style.padding.bottom;
			}
			
			_vScrollBar.position = new Point(hs, vsbp.top);
			_vScrollBar.size = new Size(0, vsbp.getHeight(vs));
			_visHeight = _style.padding.getHeight(_height);
			
			_mask = new Rectangle(0, 0, itemW, _visHeight);
			_needsRebind = true;
		}

		private function _dataBind():void {
			for each(var li:AbstractListItem in _items){
				li.dispose();
			}
			
			var m:Vector.<AbstractListItem> = _items = new Vector.<AbstractListItem>();
			if (!_dataSource || _dataSource.count < 1) return;
			
			var e:IEnumerator = _dataSource.getEnumerator(),
			r:AbstractListItem,
			p:Point = new Point(0, 0),
			lih:uint = _listItemHeight,
			h:uint = _visHeight;
			
			for (var c:uint = 0; c < _visibleItems; ++c){
				if (e.moveNext()){
					r = _itemClass.create(_params);
					_contents.addChild(r._visualUI);
					m[c] = r;
					r.position = p;
					r.onSelect = _onSelect;
					p.y += lih;
				}
			}
			
			//TODO: _isFixedHeight
			_totalHeight = lih * _dataSource.count;
			_vScrollBar.setScrollProperties(h, 0, _totalHeight - h);
			_oldStart = -1;
			_vScrollBar.scrollPosition = 0;
			_vScrollBar.onScroll.fire();
			_selectItemAt(_selIdx, true);
			_reAdjust();
			_needsRebind = false;
		}
		
		private function _reAdjust():void {
			for each(var li:AbstractListItem in _items){
				li.size = _itemSize;
			}
			
			_contents.scrollRect = _mask;
			_scrollToSelected(_selIdx);
		}
		
		private function _onSelect(pLI:AbstractListItem):void {
			focus();
			_selectItemAt(pLI.index);
		}
		
		override protected function _draw():void {
			_visHeight = _style.padding.getHeight(_height);
			_setDecoState(hasFocus() ? UIStates.ACTIVE : UIStates.NORMAL, _width, _height);
				
			if (_needsRebind) {
				setTimeout(_dataBind, 1);
				
			} else {
				_reAdjust();
			}
		}
		
		override public function focus():void {
			if (!_focused && _selectedUI){
				_tempSelIdx = _selIdx;
			}
			
			if (!_mouseWheel) {
				UIStage.addEventListener(MouseEvent.MOUSE_WHEEL, _onWheel);
				_mouseWheel = true;
			}
			
			super.focus();
		}
		
		override public function blur():void {
			super.blur();
			if (_mouseWheel){
				UIStage.removeEventListener(MouseEvent.MOUSE_WHEEL, _onWheel);
				_mouseWheel = false;
			}
		}
		
		private function _onWheel(pMe:MouseEvent):void {
			_vScrollBar.scrollPosition += -pMe.delta * _listItemHeight; 
		}
		
		public function get selectedIndex():int {
			return _selIdx;
		}
		
		public function set selectedIndex(pIndex:int):void {
			pIndex = Math.min(Math.max(pIndex, -1), _itemCount);
			
			if (_needsRebind) {
				_selIdx = pIndex;
				
			} else {
				_selectItemAt(pIndex, true);
			}
		}
		
		override protected function _keyDownHandler(pSender:KeyHandler, pArgs:KeyEventArgs):void{
			var s:int = _tempSelIdx == -1 ? _selIdx : _tempSelIdx,
			key:uint = pArgs.keyboardEvent.keyCode;
			
			if (key == Keyboard.SPACE){
				_selectItemAt(s);
				
			} else {
				switch (key){
					case Keyboard.DOWN:
						s++;
						break;
						
					case Keyboard.UP:
						s--;
						break;
						
					case Keyboard.PAGE_DOWN:
						s += _visibleItems;
						break;
						
					case Keyboard.PAGE_UP:
						s -= _visibleItems;
						break;
						
					case Keyboard.HOME:
						s = 0;
						break;
						
					case Keyboard.END:
						s = int.MAX_VALUE;
						break;
						
					default:
						var char:String = String.fromCharCode(key);
						if (char > " "){
							var t:int = getTimer();
							if ((t - _ktimer) < 500){
								_word += char;
								
							} else {
								_word = char;
							}
							
							var a:Array = Comprehension.from(_dataSource).where(_filterByName).asArray();
							if (a.length){
								s = a[0].index;
							}
							
							_ktimer = t;
						}
				}
				
				_setState(UIStates.NORMAL);
				_scrollToSelected(s);
				_setState(UIStates.HOVER);
			}	
		}
		
		private function _setState(pState:uint):void {
			for (var c:uint = 0; c < _visibleItems; c++){
				var li:AbstractListItem = _items[c];

				if (li.index == _tempSelIdx) {
					li.setState(pState);
				}
			}
		}
		
		private function _filterByName(pDS:DSItem):Boolean {
			return pDS.text.indexOf(_word) == 0;
		}

		private function _selectItemAt(pPosition:int, pForce:Boolean = false):void {
			_selIdx = _tempSelIdx = pPosition;
			_selectedDS = pPosition < 0 ? null : _dataSource.getItemAt(pPosition);
	
			if (_selectedUI && _selectedUI != li){
				_selectedUI.selected = false;
			}
			
			_selectedUI = null;
			
			for (var c:uint = 0; c < _visibleItems; ++c){
				var li:AbstractListItem = _items[c]; 
				if (li.index == pPosition) {
					li.selected = true;
					_selectedUI = li;
					break;
				}
			}
			
			if (pForce) {
				_scrollToSelected(pPosition + int(_visibleItems / 2));
			}
			
			if (_eOnChange) _eOnChange.fire();
		}
		
		private function _scrollToSelected(pSelected:int):void {
			pSelected = Math.min(Math.max(pSelected, 0), _itemCount);
			
			_tempSelIdx = pSelected;
			var oPos:int = _vScrollBar.scrollPosition,
			yPos:int = (pSelected + 1) * _listItemHeight;
			
			if ((oPos + _visHeight) < yPos){
				_vScrollBar.scrollPosition = yPos - _visHeight;
				
			} else if (yPos < (oPos + _listItemHeight)){
				_vScrollBar.scrollPosition = yPos - _listItemHeight;
			}

			_vScrollBar.onScroll.fire();
		}
		
		private function _onScroll(pSender:VScrollBar, pArgs:EventArgs):void {
			var sp:int = pSender.scrollPosition,
			lih:uint = _listItemHeight,
			startItem:uint = Math.floor(sp / lih);
			
			_mask.y = sp % lih;
			_contents.scrollRect = _mask;
			
			if (startItem == _oldStart) return;
			_oldStart = startItem;
			
			for (var c:uint = 0; c < _visibleItems; ++c){
				var ui:AbstractListItem = _items[c],
				dtItem:DSItem = _dataSource.getItemAt(c + startItem);
				if (dtItem){
					ui.text = dtItem.text;
					ui.value = dtItem.value;
					ui.index = c + startItem;
					ui.setState(UIStates.NORMAL);

					if ((ui.selected = _selectedDS == dtItem)){
						_selectedUI = ui;
					}
				}
			}
		}
		
		override public function dispose():void {
			if (_disposing) return;
			super.dispose();
			
			_dataSource = null;
			_dataBind();
			
			_vScrollBar.dispose();
			
			if (_eOnChange) _eOnChange.dispose();
		}
		
		private var _eOnChange:EventHandler;
		public function get onChange():EventHandler{
			if (_eOnChange == null) _eOnChange = new EventHandler(this);
			return _eOnChange;
		}
	}
}
